package danebrown;

import com.google.common.base.Strings;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;


/**
 * 用法：
 * javac ScanFeignPost.java
 * java ScanFeignPost /path/to/project > feign_postmap.csv
 * <p>
 * 说明：
 * - 纯文本解析，兼容多行注解、单双引号；
 * - path/value/位置参数 逐级回退；
 * - 方法签名取 @PostMapping 紧随其后的第一行非注解、非空白行；
 * - 若路径/名称使用常量或拼接，无法还原为最终值（会留空）。
 */
public class ScanFeignPost {

    public static void writeCsvToFile(List<ResultRow> rows, String fileName) {
        try {
            // 创建数据列表，包含表头
            List<List<String>> data = new ArrayList<>();

            // 添加表头
            List<String> header = Arrays.asList("消费方", "提供方", "post_path", "method_signature");
            data.add(header);

            // 添加数据行
            for (ResultRow r : rows) {
                List<String> row = Arrays.asList(
                        r.file != null ? r.file : "",
                        r.feignName != null ? r.feignName : "",
                        r.postPath != null ? r.postPath : "",
                        r.methodSignature != null ? r.methodSignature : ""
                );
                data.add(row);
            }
            XlsxWriter.write(fileName, "SysConnect", data);
            System.out.println("[INFO] Successfully wrote " + rows.size() + " rows to XLSX file: " + fileName);
        } catch (Exception e) {
            System.err.println("[ERROR] Failed to write XLSX file in ScanFeignPost: " + fileName + " - " + e.getMessage());
        }
    }

    public static void main(String[] args) throws IOException {

        scan(args.length > 0 ? args[0] : ".", null);
    }

    public static void scan(String scanRootPath, String xlsxPath) throws IOException {
        Path root = Paths.get(scanRootPath);
        List<ResultRow> rows = new ArrayList<>();

        System.out.println("\"发起方\",\"提供方\",\"post_path\",\"method_signature\"");

        // 遍历所有 .java 文件
        try (Stream<Path> paths = Files.walk(root)) {
            paths.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java"))
                    .forEach(p -> {
                        try {


                            byte[] rawbyte = Files.readAllBytes(p);
                            String raw = new String(rawbyte, StandardCharsets.UTF_8);
                            // 预过滤：不含 @FeignClient 的文件直接跳过
                            if (!raw.contains("@FeignClient")) return;

                            // 做一次简单去注释，降低误报（尽量保留换行以便行号邻近判断）
                            String code = stripComments(raw);

                            // 抓取文件级 FeignClient 名称（只取第一个，通常一个文件一个 client）
                            String feignName = extractFeignName(code);

                            if (feignName == null || feignName.isEmpty()) {
                                // 仍输出 PostMapping，但 feign_name 为空可帮助排查
                                feignName = "";
                            }
                            if (feignName.contains(":") && feignName.contains("}")) {//解决有{xxxx:yy}这种问题
                                feignName = feignName.substring(feignName.indexOf(":") + 1, feignName.lastIndexOf("}"));
                            }
                            // 遍历所有 @PostMapping(...) 片段
                            for (AnnotationSpan post : findAnnotationSpans(code, "@PostMapping")) {
                                String postPath = extractMappingPath(post.body);
                                String methodSig = extractNextMethodSignature(code, post.endIndex);

                                if (postPath == null) postPath = "";
                                if (methodSig == null) methodSig = "";
                                String beginSide = root.relativize(p.toAbsolutePath()).toString();//调用发起方

                                if(beginSide.contains(java.io.File.separator)){
                                    beginSide = beginSide.substring(0, beginSide.indexOf(java.io.File.separator));
                                }

                                rows.add(new ResultRow(
                                        feignName,
                                        beginSide,
                                        postPath,
                                        methodSig
                                ));
                            }
                        } catch (Exception e) {
                            // 出错文件也不中断整个扫描
                            System.err.println("[WARN] Failed on " + p + ": " + e.getMessage());
                        }
                    });
        }
        // 生成CSV文件名（包含时间戳）
        String timestamp = String.valueOf(System.currentTimeMillis());

        String csvFileName = "sys_connect" + timestamp + ".xlsx";
        if (!Strings.isNullOrEmpty(xlsxPath)) {
            csvFileName = xlsxPath;
        }
        writeCsvToFile(rows, csvFileName);
        // 输出 CSV
        for (ResultRow r : rows) {
            System.out.println(
                    csv(r.file) + "," +
                            csv(r.feignName) + "," +
                            csv(r.postPath) + "," +
                            csv(r.methodSignature));
        }
    }

    // ===== 工具函数：CSV 转义 =====
    static String csv(String s) {
        if (s == null) s = "";
        return "\"" + s.replace("\"", "\"\"") + "\"";
    }

    // ===== 去注释（保留字符串）=====
    // 简单状态机：去除 // 行注释 和 /* ... */ 块注释；保留字符串内的 // 或 /* */；保留换行
    static String stripComments(String src) {
        StringBuilder out = new StringBuilder(src.length());
        boolean inSL = false; // single-line comment
        boolean inBL = false; // block comment
        boolean inStr = false; // double-quoted
        boolean inChr = false; // single-quoted char literal
        for (int i = 0; i < src.length(); i++) {
            char c = src.charAt(i);
            char n = (i + 1 < src.length()) ? src.charAt(i + 1) : '\0';

            if (inSL) {
                if (c == '\n') {
                    inSL = false;
                    out.append(c);
                } else {
                    // 吃掉注释
                }
                continue;
            }
            if (inBL) {
                if (c == '*' && n == '/') {
                    inBL = false;
                    i++; // 吃掉 '/'
                } else if (c == '\n') {
                    // 保留换行，方便后续定位“下一行”
                    out.append('\n');
                }
                continue;
            }

            if (!inStr && !inChr && c == '/' && n == '/') {
                inSL = true;
                i++;
                continue;
            }
            if (!inStr && !inChr && c == '/' && n == '*') {
                inBL = true;
                i++;
                continue;
            }

            if (!inChr && c == '"' && (i == 0 || src.charAt(i - 1) != '\\')) {
                inStr = !inStr;
                out.append(c);
                continue;
            }
            if (!inStr && c == '\'' && (i == 0 || src.charAt(i - 1) != '\\')) {
                inChr = !inChr;
                out.append(c);
                continue;
            }

            out.append(c);
        }
        return out.toString();
    }

    // ===== 提取 FeignClient 名称 =====
    static String extractFeignName(String code) {
        List<AnnotationSpan> spans = findAnnotationSpans(code, "@FeignClient");
        if (spans.isEmpty()) return null;
        String body = spans.get(0).body; // 取第一个
        // 1) name=".."
        String v = pickAttrString(body, "name");
        if (isNotBlank(v)) return v;
        // 2) value=".."
        v = pickAttrString(body, "value");
        if (isNotBlank(v)) return v;
        // 3) 位置参数第一个字符串
        v = pickFirstPositionalString(body);
        return v;
    }

    // ===== 查找注解括号体 =====
    static List<AnnotationSpan> findAnnotationSpans(String code, String atName) {
        List<AnnotationSpan> list = new ArrayList<>();
        int from = 0;
        while (true) {
            int idx = code.indexOf(atName, from);
            if (idx < 0) break;
            int paren = skipWSAndFind(code, idx + atName.length(), '(');
            if (paren < 0) {
                from = idx + atName.length();
                continue;
            }
            int end = findMatchingParen(code, paren);
            if (end < 0) {
                from = idx + atName.length();
                continue;
            }
            String body = code.substring(paren + 1, end);
            list.add(new AnnotationSpan(idx, end, body));
            from = end + 1;
        }
        return list;
    }

    static int skipWSAndFind(String s, int from, char want) {
        for (int i = from; i < s.length(); i++) {
            char c = s.charAt(i);
            if (!Character.isWhitespace(c)) {
                return (c == want) ? i : -1;
            }
        }
        return -1;
    }

    static int findMatchingParen(String s, int openPos) {
        int depth = 0;
        boolean inStr = false, inChr = false;
        for (int i = openPos; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '"' && (i == 0 || s.charAt(i - 1) != '\\') && !inChr) inStr = !inStr;
            else if (c == '\'' && (i == 0 || s.charAt(i - 1) != '\\') && !inStr) inChr = !inChr;

            if (inStr || inChr) continue;

            if (c == '(') depth++;
            else if (c == ')') {
                depth--;
                if (depth == 0) return i;
            }
        }
        return -1;
    }

    // ===== 从注解体中获取属性字符串值：name=".."/'..' =====
    static String pickAttrString(String body, String key) {
        // key = "xxx"
        Pattern p1 = Pattern.compile("\\b" + Pattern.quote(key) + "\\s*=\\s*\"([^\"]+)\"");
        Matcher m = p1.matcher(body);
        if (m.find()) return m.group(1);
        // key = 'xxx'
        Pattern p2 = Pattern.compile("\\b" + Pattern.quote(key) + "\\s*=\\s*'([^']+)'");
        m = p2.matcher(body);
        if (m.find()) return m.group(1);
        return null;
    }

    // ===== 注解位置参数的第一个字符串 =====
    static String pickFirstPositionalString(String body) {
        // 允许空白与注释已处理；找第一个字符串字面量
        Pattern p1 = Pattern.compile("^\\s*\"([^\"]+)\"", Pattern.DOTALL);
        Matcher m = p1.matcher(body);
        if (m.find()) return m.group(1);
        Pattern p2 = Pattern.compile("^\\s*'([^']+)'", Pattern.DOTALL);
        m = p2.matcher(body);
        if (m.find()) return m.group(1);
        return null;
    }

    // ===== 提取 @PostMapping 路径 =====
    static String extractMappingPath(String body) {
        String v = pickAttrString(body, "path");
        if (isNotBlank(v)) return v;
        v = pickAttrString(body, "value");
        if (isNotBlank(v)) return v;
        v = pickFirstPositionalString(body);
        return v;
    }

    // ===== 提取注解之后的“方法签名起始行” =====
    static String extractNextMethodSignature(String code, int rightParenIndex) {
        // 从右括号后向前滚到行尾，然后逐行找第一个非空且不以 @ 开头的行
        int i = rightParenIndex + 1;
        // 跳到下一行开始
        while (i < code.length() && code.charAt(i) != '\n') i++;
        if (i < code.length()) i++;

        // 逐行扫描
        int start = i;
        while (start < code.length()) {
            int end = indexOfLineEnd(code, start);
            String line = code.substring(start, end).trim();
            start = (end < code.length()) ? end + 1 : end;

            if (line.isEmpty()) continue;
            if (line.startsWith("@")) continue; // 跳过其他注解
            // 简单认为这是方法签名起始（不做严格 Java 解析）
            return line;
        }
        return null;
    }

    static int indexOfLineEnd(String s, int from) {
        int n = s.indexOf('\n', from);
        return n < 0 ? s.length() : n;
    }

    static boolean isNotBlank(String s) {
        return s != null && !s.isEmpty();
    }

    // ===== 数据结构 =====
    static class ResultRow {
        String feignName;
        String file;
        String postPath;
        String methodSignature;

        ResultRow(String feignName, String file, String postPath, String methodSignature) {
            this.feignName = feignName;
            this.file = file;
            this.postPath = postPath;
            this.methodSignature = methodSignature;
        }
    }

    static class AnnotationSpan {
        int startIndex;
        int endIndex;   // 括号右侧的下标（右括号位置）
        String body;    // 括号内的内容

        AnnotationSpan(int s, int e, String b) {
            startIndex = s;
            endIndex = e;
            body = b;
        }
    }
}
